1 // Copyright 2020 Espressif Systems (Shanghai) PTE LTD
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 #include <stdlib.h>
15 #include <string.h>
16 #include <sys/cdefs.h>
17 #include "esp_log.h"
18 #include "esp_attr.h"
19 #include "esp_compiler.h"
20 #include "driver/rmt.h"
21 #include "musical_buzzer.h"
22 
23 static const char *TAG = "buzzer_rmt";
24 
25 #define BUZZER_CHECK(a, msg, tag, ret, ...)                                       \
26     do {                                                                          \
27         if (unlikely(!(a))) {                                                     \
28             ESP_LOGE(TAG, "%s(%d): " msg, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
29             ret_code = ret;                                                       \
30             goto tag;                                                             \
31         }                                                                         \
32     } while (0)
33 
34 typedef struct {
35     musical_buzzer_t parent;
36     rmt_channel_t channel;
37     uint32_t counter_clk_hz;
38     const musical_buzzer_notation_t *notation;
39     uint32_t notation_length;
40     uint32_t next_notation_index;
41 } rmt_buzzer_t;
42 
update_notation_freq_duration(rmt_buzzer_t * rmt_buzzer)43 static IRAM_ATTR rmt_item32_t update_notation_freq_duration(rmt_buzzer_t *rmt_buzzer)
44 {
45     rmt_item32_t notation_code = {.level0 = 1, .duration0 = 1, .level1 = 0, .duration1 = 1};
46     const musical_buzzer_notation_t *notation = &rmt_buzzer->notation[rmt_buzzer->next_notation_index];
47 
48     // convert frequency to RMT item format
49     notation_code.duration0 = rmt_buzzer->counter_clk_hz / notation->note_freq_hz / 2;
50     notation_code.duration1 = notation_code.duration0;
51     // convert duration to RMT loop count
52     rmt_set_tx_loop_count(rmt_buzzer->channel, notation->note_duration_ms * notation->note_freq_hz / 1000);
53 
54     rmt_buzzer->next_notation_index++;
55     return notation_code;
56 }
57 
buzzer_play(musical_buzzer_t * buzzer,const musical_buzzer_notation_t * notation,uint32_t notation_length)58 static esp_err_t buzzer_play(musical_buzzer_t *buzzer, const musical_buzzer_notation_t *notation, uint32_t notation_length)
59 {
60     esp_err_t ret_code = ESP_OK;
61     rmt_buzzer_t *rmt_buzzer = __containerof(buzzer, rmt_buzzer_t, parent);
62 
63     BUZZER_CHECK(notation, "notation can't be null", err, ESP_ERR_INVALID_ARG);
64 
65     // update notation with the new one
66     rmt_buzzer->notation = notation;
67     rmt_buzzer->next_notation_index = 0;
68     rmt_buzzer->notation_length = notation_length;
69 
70     rmt_item32_t notation_code = update_notation_freq_duration(rmt_buzzer);
71     // start TX
72     rmt_write_items(rmt_buzzer->channel, &notation_code, 1, false);
73 err:
74     return ret_code;
75 }
76 
buzzer_stop(musical_buzzer_t * buzzer)77 static esp_err_t buzzer_stop(musical_buzzer_t *buzzer)
78 {
79     rmt_buzzer_t *rmt_buzzer = __containerof(buzzer, rmt_buzzer_t, parent);
80     rmt_tx_stop(rmt_buzzer->channel);
81     return ESP_OK;
82 }
83 
buzzer_del(musical_buzzer_t * buzzer)84 static esp_err_t buzzer_del(musical_buzzer_t *buzzer)
85 {
86     rmt_buzzer_t *rmt_buzzer = __containerof(buzzer, rmt_buzzer_t, parent);
87     free(rmt_buzzer);
88     return ESP_OK;
89 }
90 
rmt_tx_loop_end(rmt_channel_t channel,void * args)91 static void rmt_tx_loop_end(rmt_channel_t channel, void *args)
92 {
93     rmt_buzzer_t *rmt_buzzer = (rmt_buzzer_t *)args;
94 
95     // stop it firstly, RMT TX engine won't stop automatically in loop mode
96     rmt_tx_stop(rmt_buzzer->channel);
97 
98     // update rmt loop freq and duration if the notation doesn't reach the end
99     if (rmt_buzzer->next_notation_index < rmt_buzzer->notation_length) {
100         rmt_item32_t notation_code = update_notation_freq_duration(rmt_buzzer);
101         // issue a new TX transaction
102         rmt_write_items(rmt_buzzer->channel, &notation_code, 1, false);
103     }
104 }
105 
musical_buzzer_create_rmt(const musical_buzzer_config_t * config,musical_buzzer_t ** ret_handle)106 esp_err_t musical_buzzer_create_rmt(const musical_buzzer_config_t *config, musical_buzzer_t **ret_handle)
107 {
108     esp_err_t ret_code = ESP_OK;
109     rmt_buzzer_t *rmt_buzzer = NULL;
110     BUZZER_CHECK(config, "configuration can't be null", err, ESP_ERR_INVALID_ARG);
111     BUZZER_CHECK(ret_handle, "can't assign handle to null", err, ESP_ERR_INVALID_ARG);
112 
113     rmt_buzzer = calloc(1, sizeof(rmt_buzzer_t));
114     BUZZER_CHECK(rmt_buzzer, "allocate context memory failed", err, ESP_ERR_NO_MEM);
115 
116     rmt_buzzer->channel = (rmt_channel_t)config->dev;
117 
118     rmt_get_counter_clock(rmt_buzzer->channel, &rmt_buzzer->counter_clk_hz);
119 
120     // register tx end callback function, which got invoked when tx loop comes to the end
121     rmt_register_tx_end_callback(rmt_tx_loop_end, rmt_buzzer);
122 
123     rmt_buzzer->parent.del = buzzer_del;
124     rmt_buzzer->parent.play = buzzer_play;
125     rmt_buzzer->parent.stop = buzzer_stop;
126 
127     *ret_handle = &(rmt_buzzer->parent);
128     return ESP_OK;
129 
130 err:
131     if (rmt_buzzer) {
132         free(rmt_buzzer);
133     }
134     return ret_code;
135 }
136