SPI Driven Audio Circuit w/ Frequency Selection

Assembled Audio Module based on GY-9833 Frequency Generator.

The PCBs I had ordered from OSH Park arrived, and my co-worker Mike was nice enough to solder the components on for me. The module works, and I was able to produce 1000Hz and 2000Hz audio using FlashForth to drive the SPI signals. The sound output sounds pure and pleasant, although when I twist the volume above halfway, my ears detect what sounds like a very slight overtone or distortion, but it still is pleasant-sounding enough.

The module is a step above the usual cheap piezo buzzer circuit, the latter having the grating, distorted sound which is usually undesirable. But it is a few steps below having an actual programmable sound generator chip or a full blown sound card.

The actual PCB design here would not be used in a commercial product as it is very large for its functionality (about 6 cm wide). But it could be constructed easily by any hobbyist as the components are all through-hole and spaced far apart.

Link to design on OSH Park:

https://oshpark.com/shared_projects/2lO7K5tc

Parts list:

Schematic:

KiCAD files:

These are the trimpots that fit the board:

https://www.amazon.com/gp/product/B071WW6VN8/

The GY-9833 module is also available from several manufacturers through Amazon and other Web stores.

Audio Circuit: Single Frequency Output Selection

Audio Circuit Driven by Op Amp, GY-9833 frequency synthesizer, and Arduino

Above is the circuit I mentioned earlier had been intending to design with a small 3W speaker driven by an LM386 op amp driven by an AD9833 frequency synthesizer chip, driven by SPI (sourced in the schematic from an Arduino Uno).

I thought designing and testing this simple circuit would only take an hour or two. But I got hung up for a lot of hours because, in the breadboard, I was getting weird spurious signals in the output when I had the the circuit set to certain volume levels, and I couldn’t figure out what the problem was. In the end, the problem was fixed by putting a large capacitor across +5V and AGND. After that, this output sounded pure and pleasant, testing with 1000Hz, 1500Hz, and 2000Hz signals. I do not know if it is really necessary to use a capacitor quite that big, but I didn’t feel like doing additional testing.

I had thoughts to turn the circuit into a PCB, though it would be through-hole (easier to assemble) and not a miniature surface-mount design like would be seen in a commercial application. This would be around 6cm x 5cm, with the speaker plugging in on the right pins and the Arduino plugging in on the left header. The GY-9833 module would slide onto the center pins and could be soldered on to them.

I can provide design files later after I finish laying out the traces. I can also post some FlashForth code later.

Quick and Dirty Complex Functions of Time with Guile + Gnuplot

f(t) = e^(st) where s = 0+i

Inspired by a great video on the Laplace transform, I was looking for an easy way to visualize complex functions of time, i.e., functions of time where the output is a complex number. This is fairly easy to do with Guile Scheme and Gnuplot, though the method could be polished more into a proper application, which I didn’t do.

This is the core function for plotting the data:

(define (complex-plot t0 tmax step f)
  (unless (> t0 tmax)
    (let ((z (f t0)))
      (display t0)
      (display " ")
      (display (real-part z))
      (display " ")
      (display (imag-part z))
      (display "\r\n")
      (complex-plot (+ t0 step) tmax step f))))

We just pass in the starting value (t0), the maximum time value, a step value (usually some fraction of Pi), and a lambda with our complex function of time. Writing complex functions of time is pretty easy in Guile Scheme because all the numbers are represented internally as complex numbers anyway. E.g.:

scheme@(guile-user)> (complex-plot 0 25 (/ 3.1415 32) (lambda (t) (exp (* 0+i t))))
0 1.0 0.0
0.098171875 0.9951850104692727 0.09801425884672994
0.19634375 0.9807864101254524 0.19508464243304188
0.294515625 0.9569428571883648 0.2902763649975122
0.3926875 0.9238839645735444 0.3826727322450213
...etc...

We have to output this to a file:

(with-output-to-file "out.txt" (lambda () (complex-plot 0 25 (/ 3.1415 32) (lambda (t) (exp (* 0+i t))))))

Then we start gnuplot and use a 3d splot with lines. (Credit goes to this tutorial.)

gnuplot> set xrange [0:25]
gnuplot> set yrange [-2:2]
gnuplot> set ticslevel 0
gnuplot> splot "out.txt" u 1:2:3 with lines

Another interesting complex function of time is the addition of two complex functions of time which leave only a signal on the real axis:

(lambda (t) (+ (exp (* 0+i t)) (exp (* 0-i t))))
e^(s_0 * t) + e^(s_1 * t) where s_0 = 0+i and s_1 = 0-i

And here is my hacker emacs screen, where the action is happening!

hacker emacs screenshot

Moiré Patterns: Square and Hexagon

Overlaid hexagon grids with moderate rotational offset

A square grid with a slight rotational offset gives a square or diamond zig-zag grid:

With smaller squares, the checkerboard pattern is more interesting:

Hexagonal grids give an interesting variety of overlap patterns in one image:

The new code is below:

;; Copyright 2021 Christopher Howard

;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
;; You may obtain a copy of the License at

;;     http://www.apache.org/licenses/LICENSE-2.0

;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.

(define* (draw-square-block r orig-x orig-y base-l
                         #:key (rotr 0))
  (let* ((lrot (if (zero? rotr) (lambda (x) x)
                   (lambda (c) (rot rotr c))))
         (ccomp (lambda (c) (cex (lrot c)))))
    (render-draw-lines
     r
     (map ccomp (list (list orig-x orig-y)
                      (list (+ orig-x base-l) orig-y)
                      (list (+ orig-x base-l) (+ orig-y base-l)))))))

(define* (draw-square-pattern r offset-x offset-y #:key (rotr 0))
  (let* ((base-l 10))
    (do ((j 0 (1+ j)))
        ((> j 80))
        (do ((i 0 (1+ i)))
            ((> i 80))
          (draw-square-block
           r
           (+ (* i base-l) offset-x)
           (+ (* j base-l) offset-y)
           base-l
           #:rotr rotr)))))

(define* (draw-hexagon-block r orig-x orig-y base-l
                         #:key (rotr 0))
  (let* ((base-hl (* 0.5 base-l))
         (base-h (* (* base-l (sqrt 3)) 0.5))
         (lrot (if (zero? rotr) (lambda (x) x)
                   (lambda (c) (rot rotr c))))
         (ccomp (lambda (c) (cex (lrot c)))))
    (render-draw-lines
     r
     (map ccomp (list (list orig-x orig-y)
                      (list (- orig-x base-hl)
                            (+ orig-y base-h))
                      (list orig-x
                            (+ orig-y (* 2 base-h)))
                      (list (+ orig-x base-l)
                            (+ orig-y (* 2 base-h)))
                      (list (+ orig-x base-l base-hl)
                            (+ orig-y base-h))
                      (list (+ orig-x base-l)
                            orig-y))))
    (render-draw-lines
     r
     (map ccomp (list (list (- orig-x base-hl)
                            (+ orig-y base-h))
                      (list (- orig-x base-hl base-l)
                            (+ orig-y base-h)))))))

(define* (draw-hexagon-pattern r offset-x offset-y #:key (rotr 0))
  (let* ((base-l 20)
         (base-h (* (* base-l (sqrt 3)) 0.5))
         (base-hl (* 0.5 base-l)))
    (do ((j 0 (1+ j)))
        ((> j 40))
        (do ((i 0 (1+ i)))
            ((> i 40))
          (draw-hexagon-block
           r
           (+ offset-x
              (* i 3 base-l))
           (+ offset-y (* j 2 base-h))
           base-l
           #:rotr rotr)))))

Moiré Patterns

Moiré pattern with two overlaid triangle tilings with a slight rotation offset

An interesting thing to play around with is Moiré patterns, which are new patterns you get when you overlay two identical patterns but with some slight displacement. The above and below images are using two triangle tilings. My code displays the tilings and allows rotational and translation adjustment using some keyboard keys. The pattern for the overlaid triangle tilings seems to be generally some number of hexagons at various sizes.

Pattern with less hexagons.

This was the most interesting, however, was this dodecagon-like shape when around a 30 degree rotation offset or so.

Pattern with dodecagon-like shapes.
;; Copyright 2021 Christopher Howard

;; Licensed under the Apache License, Version 2.0 (the "License");
;; you may not use this file except in compliance with the License.
;; You may obtain a copy of the License at

;;     http://www.apache.org/licenses/LICENSE-2.0

;; Unless required by applicable law or agreed to in writing, software
;; distributed under the License is distributed on an "AS IS" BASIS,
;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;; See the License for the specific language governing permissions and
;; limitations under the License.

(use-modules (sdl2)
             (sdl2 video)
             (sdl2 rect)
             (sdl2 surface)
             (sdl2 render)
             (sdl2 input keyboard)
             (sdl2 events))

(define (cex coord) (list (inexact->exact (round (car coord)))
                          (inexact->exact (round (cadr coord)))))

(define (rot rad coords)
  (let* ((x (car coords))
         (y (cadr coords))
         (cpx (make-rectangular x y))
         (fact (exp (* rad 0+i)))
         (ncpx (* cpx fact)))
    (list (real-part ncpx) (imag-part ncpx))))

(define* (draw-tri-block r orig-x orig-y base-l
                         #:key (rotr 0))
  (let* ((base-hl (* 0.5 base-l))
         (base-h (* (* base-l (sqrt 3)) 0.5))
         (lrot (if (zero? rotr) (lambda (x) x)
                   (lambda (c) (rot rotr c))))
         (ccomp (lambda (c) (cex (lrot c)))))
    (render-draw-lines
     r
     (map ccomp (list (list (+ orig-x base-l) orig-y)
                      (list (+ orig-x base-hl)
                            (+ orig-y base-h))
                      (list (+ orig-x base-hl base-l)
                            (+ orig-y base-h))
                      (list (+ orig-x base-l) (ex orig-y)))))))

(define* (draw-tri-pattern r offset-x offset-y #:key (rotr 0))
  (let* ((base-l 20)
         (base-h (* (* base-l (sqrt 3)) 0.5))
         (base-hl (* 0.5 base-l)))
    (do ((j 0 (1+ j)))
        ((> j 40))
        (do ((i 0 (1+ i)))
            ((> i 40))
          (draw-tri-block
           r
           (+ (* i base-l)
              (if (zero? (floor-remainder j 2)) 0 base-hl)
              offset-x)
           (+ (* j base-h) offset-y)
           base-l
           #:rotr rotr)))))

(define rot-radius 0)

(define rot-inc (/ 3.1415 5096))

(define trans-x 0)

(define trans-y 0)

(define trans-inc 1)

(define (main)
  (sdl-init)
  (let* ((mywindow (make-window
                  #:title "Super Awesome Window"
                  #:opengl? #t
                  #:size '(1024 768)))
         (glc (make-gl-context mywindow))
         (myrect (make-rect 100 100 200 100))
         (mysurface (make-rgb-surface 300 300 32))
         (myrenderer (make-renderer mywindow)))
    (set-gl-swap-interval! 'vsync)
    (while (not (key-pressed? 'q))
      (usleep 100)
      (poll-event)
      (if (key-pressed? 'r)
          (set! rot-radius (+ rot-inc rot-radius)))
      (if (key-pressed? 't)
          (set! rot-radius (- rot-radius rot-inc)))
      (if (key-pressed? 'j)
          (set! trans-x (- trans-x trans-inc)))
      (if (key-pressed? 'k)
          (set! trans-x (+ trans-x trans-inc)))
      (if (key-pressed? 'i)
          (set! trans-y (- trans-y trans-inc)))
      (if (key-pressed? 'm)
          (set! trans-y (+ trans-y trans-inc)))
      (set-render-draw-color myrenderer 0 0 0 255)
      (clear-renderer myrenderer)
      (set-render-draw-color myrenderer 0 255 255 255)
      (draw-tri-pattern myrenderer 0 0)
      (draw-tri-pattern myrenderer trans-x trans-y #:rotr rot-radius)
      (present-renderer myrenderer))
    (close-window! mywindow)
  (sdl-quit)))