├── IR_LED_driver.png ├── main ├── component.mk └── rmt_server.c ├── README.md └── demoWebPage └── ESP32-RMT-server.html /IR_LED_driver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kimeckert/ESP32-RMT-server/HEAD/IR_LED_driver.png -------------------------------------------------------------------------------- /main/component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32-RMT-server 2 | WiFi server that drives low-level commands to the ESP32 RMT peripheral 3 | 4 | __NOTE: This code seems to work reliably and has all the features that I plan to implement. It is released.__ 5 | 6 | The ESP32 receives an HTTP POST request and decodes commands contained in the request body. 7 | These commands control the RMT peripheral and drive an infrared LED. 8 | The commands are low-level mark and space commands. 9 | There is no protocol decode implemented in this application. 10 | This allows the RMT to transmit virtually any infrared protocol. 11 | Compiles with [ESP-IDF](https://github.com/espressif/esp-idf) 12 | 13 | * The HTTP POST request body consists of one or more text lines. Lines are separated by newline (\n) characters. Returns (\r) are allowed, but are ignored. 14 | * Each line consists of comma-separated values. There is one command on each line. Each line starts with one character that identifies the type of command for this line. 15 | 16 | The intent is that a JavaScript program contained in an HTML file, running on a web browser and served by a host machine (not the ESP32) will create these low-level RMT commands and transmit them to the ESP32. An example HTML file is part of this repository. A real application will have its own repository. 17 | 18 | # Commands 19 | 20 | The commands closely follow the RMT register/RAM definitions. Refer to the ESP32 documentation at http://esp32.net/ . 21 | Lines that do not follow these specifictions are silently ignored and the next line is processed. 22 | * __c,[div],[high],[low]__ 23 | * Sets the RMT channel clock frequency and the carrier clock frequency and duty cycle. 24 | * __[div]__: RMT channel clock divider (RMT_DIV_CNT_CHn). Text representation of an integer between 1 and 255, inclusive. Defines the RMT channel clock division ratio. The channel clock is divided from the source clock. The ESP32 source clock is 80MHz. 25 | * __[high]__ and __[low]__: High (RMT_CARRIER_HIGH_CHn) and low (RMT_CARRIER_LOW_CHn) duration of the carrier waveform. Text representation of integers between 1 and 65,535, inclusive. The unit of carrier_high/low is one source clock period. 26 | * __t,[non-zero duration], ... ,0__ 27 | * Defines a sequence of durations of IR transmission bits, both modulated at the carrier frequency (mark) and idle (space). 28 | * __[non-zero duration]__: A text representation of a non-zero integer between -32,767 and 32,767, inclusive. These integers define the durations of transmit marks and spaces. A zero value denotes the end of the transmission sequence. 29 | * Positive integers create a burst of IR output (mark) at the carrier frequency, with duration of [non-zero-duration] channel clock periods. 30 | * Negative integers create a duration on the IR output with no carrier (space). The duration is in channel clock periods. 31 | * Positive and negative values do not need to be alternated. Either positive or negative values can follow a positive or negative value. 32 | * A zero value tells the RMT to stop the RMT transmission sequence. The last value on the line must be a zero. A zero anywhere else on the line will terminate the transmission sequence at that point. When terminated, a new RMT transmission can be started with a new command on a new line. There will be an indeterminate delay between consecutive RMT transmissions. 33 | * The RMT RAM can store a maximum of 128 duration values. If a line contains more than 128 values, the driver implements the RMT wrap-around mode to transmit the longer sequence. The terminating zero duration counts as one of the duration values. 34 | * __d,[milliseconds delay]__ 35 | * __[milliseconds delay]__: Create a delay before decoding the next line. Used to create a realtively long gap between IR tramsmissions. Text representation of a positive non-zero integer of the number of milliseconds for the delay. Implemented with the FreeRTOS function: 36 | * vTaskDelay( milliseconds delay / portTICK_PERIOD_MS ); and has the limitations of that function. See the documentation for this function for the maximum possible delay, which depends on the tick period of your system and the data type used for this function. If your tick period is 1mS and the data type uses a 16-bit unsigned int, the maximum duration is about 65 seconds. 37 | 38 | # Definitions 39 | * __HTTP POST Request__: A combination of request header and request body, sent to the ESP32. Your web browser automatically inserts a blank line between the header and body. 40 | * __Request Header__: Your browser creates most of this. Important options for this application are: 41 | * __Request Type__: POST. Needed to avoid CORS restrictions and to contain enough information for the ESP32. 42 | * __Content-Type__: text/plain. Needed to avoid CORS restrictions. 43 | * __Request Body__: One or more lines of text, following the format described above. 44 | * __Lines__: In this application, each line in the request body consists of multiple text values, separated by commas. All of the numbers transmitted to the ESP32 are text representations of decimal numbers. Each line is terminated by either: 45 | * a newline (\n), or 46 | * a return (\r) and a newline (\n), or 47 | * the end of the request body 48 | * __Same-Origin-Policy__: Keeps your compliant browser from fetching information from a different server than the original web page was served from. This is an important internet security feature. 49 | * __CORS__: Cross-Orgin-Resource-Sharing. A complex way to allow communications between different web hosts. This is the authorized way to skirt the Same-Origin-Policy. See the WiKi for more information. 50 | -------------------------------------------------------------------------------- /demoWebPage/ESP32-RMT-server.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 12 | 13 | 14 |This is an example of how JavaScript can sent POST commands directly to an ESP32 that is running 134 | the application ESP32-RMT-server. See the description on 135 | https://github.com/kimeckert/ESP32-RMT-server. 137 | Use one of the following tools to compose and transmit commands to the ESP32. 138 |
139 | 140 |Enter the IP address of your ESP32: 144 | 145 | , such as '192.168.1.25'. 146 | This software needs the IP address to send the POST command.
147 | 148 |Enter one or more lines of text to send to the RMT.
152 | Each line can be any command that the ESP32-RMT-server can receive.
153 | All lines are sent in one POST message when you click the SEND button.
154 |
155 |
156 |
157 |
158 |
| Item | 168 |Value | 169 |Units | 170 |Description |
|---|---|---|---|
| ESP32 Clock frequency | 172 |173 | | MHz | 174 |Source Clock |
| RMT_DIV_CNT_CHn | 176 |177 | | 178 | | Channel clock divisor |
| Channel Clock Frequency | 180 |181 | | KHz | 182 |= Source Clock frequency / RMT_DIV_CNT_CHn |
| RMT_CARRIER_HIGH_CHn | 184 |185 | | 186 | | Carrier clock high duration, in Source Clock periods |
| RMT_CARRIER_LOW_CHn | 188 |189 | | 190 | | Carrier clock low duration, in Source Clock periods |
| Carrier clock frequency | 192 |193 | | KHz | 194 |= Source Clock Frequency / (RMT_CARRIER_HIGH_CHn + RMT_CARRIER_LOW_CHn) |
| Carrier clock duty cycle | 196 |197 | | Percent | 198 |= RMT_CARRIER_HIGH_CHn / (RMT_CARRIER_HIGH_CHn + RMT_CARRIER_LOW_CHn) |
| Pulse Duration Resolution | 200 |201 | | Microseconds | 202 |= One Channel clock period |
| Pulse Maximum Duration | 204 |205 | | Microseconds | 206 |= (2^15) * Channel clock period |
| RMT_server command | 208 |209 | | 210 | | Send the clock command to the ESP32 |
"; 46 | const static char http_debug3[] = ""; 47 | 48 | // how to connect to my local WiFi 49 | #define WIFI_SSID "Your SSID Here" 50 | #define WIFI_PASS "Your password here" 51 | 52 | // RMT values 53 | #define RMT_TX_CHANNEL RMT_CHANNEL_0 54 | #define RMT_TX_GPIO GPIO_NUM_26 55 | // channel clock period = 1 uS 56 | #define RMT_CLK_DIV 80 57 | 58 | // WIFI values 59 | static EventGroupHandle_t wifi_event_group; 60 | const int CONNECTED_BIT = BIT0; 61 | 62 | // used when parsing integers from character arrays 63 | typedef struct { 64 | int num; 65 | bool good; 66 | } returnIntVal; 67 | 68 | // WiFi event handler 69 | static esp_err_t event_handler(void *ctx, system_event_t *event) { 70 | switch(event->event_id) { 71 | case SYSTEM_EVENT_STA_START: 72 | esp_wifi_connect(); 73 | break; 74 | case SYSTEM_EVENT_STA_GOT_IP: 75 | xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); 76 | break; 77 | case SYSTEM_EVENT_STA_DISCONNECTED: 78 | /* This is a workaround as ESP32 WiFi libs don't currently 79 | auto-reassociate. */ 80 | esp_wifi_connect(); 81 | xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); 82 | break; 83 | default: 84 | break; 85 | } 86 | return ESP_OK; 87 | } 88 | 89 | // initialize WiFi 90 | static void initialise_wifi(void) { 91 | tcpip_adapter_init(); 92 | wifi_event_group = xEventGroupCreate(); 93 | ESP_ERROR_CHECK( esp_event_loop_init( event_handler, NULL ) ); 94 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 95 | ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); 96 | ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) ); 97 | wifi_config_t wifi_config = { 98 | .sta = { 99 | .ssid = WIFI_SSID, 100 | .password = WIFI_PASS, 101 | }, 102 | }; 103 | ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); 104 | ESP_ERROR_CHECK( esp_wifi_set_config( WIFI_IF_STA, &wifi_config ) ); 105 | ESP_ERROR_CHECK( esp_wifi_start() ); 106 | printf("WiFi initialized\n"); 107 | } 108 | 109 | // initialize RMT peripheral for output 110 | // Note that some of these settings can be modified during operation 111 | static void rmt_tx_init() { 112 | rmt_config_t rmt_tx; 113 | rmt_tx.rmt_mode = RMT_MODE_TX; 114 | rmt_tx.channel = RMT_TX_CHANNEL; 115 | rmt_tx.gpio_num = RMT_TX_GPIO; 116 | rmt_tx.mem_block_num = 1; 117 | rmt_tx.clk_div = RMT_CLK_DIV; 118 | rmt_tx.tx_config.loop_en = false; 119 | rmt_tx.tx_config.carrier_duty_percent = 30; 120 | rmt_tx.tx_config.carrier_freq_hz = 38000; 121 | rmt_tx.tx_config.carrier_level = RMT_CARRIER_LEVEL_HIGH; 122 | rmt_tx.tx_config.carrier_en = true; 123 | rmt_tx.tx_config.idle_level = RMT_IDLE_LEVEL_LOW; 124 | rmt_tx.tx_config.idle_output_en = true; 125 | 126 | ESP_ERROR_CHECK( rmt_config(&rmt_tx) ); 127 | ESP_ERROR_CHECK( rmt_driver_install(rmt_tx.channel, 0, 0) ); 128 | printf("RMT initialized\n"); 129 | } 130 | 131 | // find the start of the request body, which follows a blank line 132 | // a blank line is created by two consecutive \n's, ignoring \r's 133 | // more than two consecutive newlines are accepted 134 | // returns the index of the character following the newlines 135 | u16_t find_body( char* buf, u16_t length ) { 136 | printf("Finding POST body, buffer size %d\n", length); 137 | // counts number of consecutive newlines 138 | u16_t newlines = 0; 139 | // character pointer 140 | u16_t this_char; 141 | // the response starts with 'POST ', so start at character 5 142 | for ( this_char=5; this_char<=length; this_char++ ) { 143 | if ( buf[this_char] == '\r' ) { continue; } 144 | if ( buf[this_char] == '\n' ) { newlines++; } 145 | // only received a single newline, reset counter 146 | else if ( newlines == 1 ) { newlines = 0; } 147 | if ( ( newlines > 1 ) && ( buf[this_char] != '\n' ) ) { 148 | printf("Found body start %d\n", this_char); 149 | return this_char; 150 | } 151 | } 152 | // body not found 153 | return 0; 154 | } 155 | 156 | // push item onto rmt_item32_t array 157 | // value_index points to: duration0/level0, duration1/level1, ... 158 | // value_index: 0 1 2 3 4 5 6 7 159 | // value_index / 2: 0 0 1 1 2 2 3 3 160 | // items index: 0 0 1 1 2 2 3 3 161 | void pushItem( rmt_item32_t* items, u16_t value_index, bool level, u16_t duration ) { 162 | u16_t i = value_index / 2; 163 | // lower 16 bits of the 32-bit item 164 | if ( value_index % 2 == 0 ) { 165 | items[i].level0 = level; 166 | items[i].duration0 = duration; 167 | // just in case this is the last item, fill up the next entry 168 | items[i].level1 = level; 169 | items[i].duration1 = 0; 170 | } 171 | // upper 16 bits of the 32-bit item 172 | else { 173 | items[i].level1 = level; 174 | items[i].duration1 = duration; 175 | } 176 | printf("Item pushed to RMT data RAM: level %d, duration %u\n", level, duration); 177 | } 178 | 179 | // set the clock divisor and carrier clock high/low durations 180 | // set during the decode of RMT data 181 | void set_rmt_frequency( uint16_t div, uint16_t high, uint16_t low ) { 182 | printf("Set frequency %i, %i, %i\n", div, high, low); 183 | 184 | // if clock divisor changes, set to new value 185 | uint8_t previous_div; 186 | rmt_get_clk_div( RMT_TX_CHANNEL, &previous_div ); 187 | printf("previous div: %i\n", previous_div); 188 | if ( (uint8_t) div != previous_div ) { 189 | rmt_set_clk_div( RMT_TX_CHANNEL, (uint8_t) div ); 190 | printf("set div = %i\n", div); 191 | } 192 | 193 | // set clock high and low times 194 | // args: channel, carrier_en, high_level, low_level, carrier_level 195 | rmt_set_tx_carrier( RMT_TX_CHANNEL, true, high, low, RMT_CARRIER_LEVEL_HIGH ); 196 | printf("Reset RMT frequency\n"); 197 | } 198 | 199 | // returns the number of comma-separated fields in this line, 200 | // including the first field, the line type designation 201 | u16_t count_values( char* buf, u16_t start, u16_t end ) { 202 | u16_t count = 0; 203 | u16_t pointer; 204 | for ( pointer=start; pointer<=end; pointer++ ) { 205 | if ( buf[pointer] == ',' ) { count++; } 206 | } 207 | return count + 1; 208 | } 209 | 210 | // get the n-th field from the comma-separated line 211 | // the first numerical field is index = 0 212 | returnIntVal get_number( char* buf, u16_t start, u16_t end, int count ) { 213 | returnIntVal n; 214 | n.num = 0; 215 | n.good = true; 216 | bool is_neg = false; 217 | u16_t this_char = start; 218 | u16_t commas = 0; 219 | printf("get_number: look for %d\n", count); 220 | 221 | // count values separated by commas 222 | if ( count > 0 ) { 223 | while( this_char++ <= end ) { 224 | if ( buf[this_char] == ',' ) { commas++; } 225 | if ( commas == count ) { break; } 226 | } 227 | // point to the character after the comma 228 | this_char++; 229 | } 230 | 231 | // did not find the value 232 | if ( commas < count ) { 233 | printf("get_number not found\n"); 234 | n.good = false; 235 | return n; 236 | } 237 | 238 | // is the number negative? 239 | if ( buf[this_char] == '-' ) { 240 | is_neg = true; 241 | this_char++; 242 | } 243 | 244 | // get numerical characters 245 | while( (this_char <= end) && 246 | (buf[this_char]>47) && 247 | (buf[this_char]<58 ) ) { 248 | n.num = ( 10 * n.num ) + ( buf[this_char] - 48 ); 249 | this_char++; 250 | } 251 | 252 | // return 253 | if ( is_neg ) { n.num = -1 * n.num; } 254 | // printf("get_number found %d\n", n.num); 255 | return n; 256 | } 257 | 258 | // write frequency and clock data to the RMT peripheral 259 | void send_freq( char* buf, u16_t start, u16_t end ) { 260 | // frequency settings: [Division Ratio, High Duration, Low Duration] 261 | int freq[3]; 262 | bool good = true; 263 | returnIntVal n; 264 | printf("send_freq %d to %d\n", start, end); 265 | 266 | // division ratio, 8-bit register 267 | n = get_number( buf, start, end, 0 ); 268 | if ( (n.good) && (n.num>0) && (n.num<256) ) { 269 | freq[0] = n.num; 270 | // printf("get_num 0 %d\n", n.num); 271 | } 272 | else { good = false; } 273 | 274 | // clock high duration, 16-bit register 275 | n = get_number( buf, start, end, 1 ); 276 | if ( (n.good) && (n.num>0) && (n.num<65536) ) { 277 | freq[1] = n.num; 278 | // printf("get_num 1 %d\n", n.num); 279 | } 280 | else { good = false; } 281 | 282 | // clock low duration, 16-bit register 283 | n = get_number( buf, start, end, 2 ); 284 | if ( (n.good) && (n.num>0) && (n.num<65536) ) { 285 | freq[2] = n.num; 286 | // printf("get_num 2 %d\n", n.num); 287 | } 288 | else { good = false; } 289 | 290 | // set register values 291 | if ( good ) { 292 | set_rmt_frequency( freq[0], freq[1], freq[2] ); 293 | //printf("Set frequency %d, %d, %d\n", freq[0], freq[1], freq[2]); 294 | } 295 | } 296 | 297 | // write duration data to the RMT RAM 298 | void send_duration( char* buf, u16_t start, u16_t end ) { 299 | u16_t count, c; 300 | returnIntVal val; 301 | bool good = true; 302 | 303 | // the number of comma-separated numeric values on this line 304 | count = count_values(buf, start, end); 305 | 306 | // each 32-bit RMT item holds 2 duration/level values 307 | // divide count of numerical values by 2 get required number of 32-bit values 308 | // multiply by 4 to get bytes 309 | // count: 0 1 2 3 4 5 6 7 8 310 | // count + 1: 1 2 3 4 5 6 7 8 9 311 | // (count + 1) / 2: 0 1 1 2 2 3 3 4 4 312 | // RMT RAM items: 0 1 1 2 2 3 3 4 4 313 | // malloc bytes: 0 4 4 8 8 12 12 16 16 314 | 315 | // number of bytes in the 32-bit RMT item array 316 | size_t size = 4 * ( ( count + 1 ) / 2 ); 317 | rmt_item32_t* items = (rmt_item32_t*) malloc(size); 318 | printf("Row has %i values, allocated %i bytes\n", count, size); 319 | printf("Starts with %c, ends with %c\n", buf[start], buf[end]); 320 | 321 | // create an object for RMT RAM values 322 | for ( c=0; c