In the context of an authorization code grant, the main purpose of the client secret is to prevent client impersonation. That is to say, it prevents a malicious client from pretending to be a legitimate client in order to get an access token from the resource owner under false pretenses.
It is worth pointing out that using a client secret is not the only means available to prevent client impersonation. For public clients (such as mobile apps and single page apps), client impersonation is prevented by the use of fixed https redirect uris. By only allowing specific, https redirect uris, a malicious client attempting to act as a legitimate client would be unable to intercept the authorization code. By being unable to intercept the authorization code, the malicious client would be unable to make the request to the token endpoint.
PKCE solves a different problem - basically, it prevents a malicious client that has somehow received an authorization code from using it to request an access token from the token endpoint. This was originally recognized as a problem in the mobile app space, but it is a useful technique for single page apps too. There is actually no harm in using PKCE with every OAuth 2 client, even those which use a client secret. The OAuth 2 Best Current Practice recommends using it everywhere.